﻿//
// (C) Copyright 2009 Irantha Suwandarathna (iranthas@hotmail.com)
// All rights reserved.
//

/********************************************************
 * ADO.NET 2.0 Data Provider for sqlLite Version 3.X
 * Written by Robert Simpson (robert@blackcastlesoft.com)
 * 
 ********************************************************/

using System;
using System.Collections.Generic;
using EffiProz.Core;

namespace System.Data.EffiProz
{
    internal static class EfzConnectionPool
    {
        /// <summary>
        /// Keeps track of connections made on a specified file.  The PoolVersion dictates whether old objects get
        /// returned to the pool or discarded when no longer in use.
        /// </summary>
        internal class Pool
        {
            internal readonly Queue<WeakReference> Queue = new Queue<WeakReference>();
            internal int PoolVersion;
            internal int MaxPoolSize;
            internal int MinPoolSize;
            internal string ConnectionString;

            internal Pool(int version, string connectionString, int maxSize, int minSize)
            {
                PoolVersion = version;
                ConnectionString = connectionString;
                MaxPoolSize = maxSize;
                MinPoolSize = minSize;
            }
        }

        /// <summary>
        /// The connection pool object
        /// </summary>
        private static SortedList<string, Pool> _connectionPools = new SortedList<string, Pool>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// The default version number new pools will get
        /// </summary>
        private static int _poolVersion = 1;

        /// <summary>
        /// Attempt to pull a pooled connection out of the queue for active duty
        /// </summary>
        /// <param name="fileName">The filename for a desired connection</param>
        /// <param name="maxPoolSize">The maximum size the connection pool for the filename can be</param>
        /// <param name="version">The pool version the returned connection will belong to</param>
        /// <returns>Returns NULL if no connections were available.  Even if none are, the poolversion will still be a valid pool version</returns>
        internal static EfzConnectionProxy Remove(string connectionString, int maxPoolSize,int minPoolSize, out int version)
        {
            lock (_connectionPools)
            {
                Pool queue;

                // Default to the highest pool version
                version = _poolVersion;

                // If we didn't find a pool for this file, create one even though it will be empty.
                // We have to do this here because otherwise calling ClearPool() on the file will not work for active connections
                // that have never seen the pool yet.
                if (_connectionPools.TryGetValue(connectionString, out queue) == false)
                {
                    queue = new Pool(_poolVersion,connectionString, maxPoolSize,minPoolSize);
                    _connectionPools.Add(connectionString, queue);

                    return null;
                }

                // We found a pool for this file, so use its version number
                version = queue.PoolVersion;
                queue.MaxPoolSize = maxPoolSize;

                ResizePool(queue, false);

                // Try and get a pooled connection from the queue
                while (queue.Queue.Count > 0)
                {
                    WeakReference cnn = queue.Queue.Dequeue();
                    EfzConnectionProxy hdl = cnn.Target as EfzConnectionProxy;
                    if (hdl != null && hdl.Created + hdl.LifeTime > DateTime.UtcNow.Ticks)
                    {
                        hdl.IsPooled = false;
                        return hdl;
                    }
                }
                return null;
            }
        }

        /// <summary>
        /// Clears out all pooled connections and rev's up the default pool version to force all old active objects
        /// not in the pool to get discarded rather than returned to their pools.
        /// </summary>
        internal static void ClearAllPools()
        {
            lock (_connectionPools)
            {
                foreach (KeyValuePair<string, Pool> pair in _connectionPools)
                {
                    while (pair.Value.Queue.Count > 0)
                    {
                        WeakReference cnn = pair.Value.Queue.Dequeue();
                        EfzConnectionProxy hdl = cnn.Target as EfzConnectionProxy;
                        if (hdl != null)
                        {
                            try
                            {
                                hdl.Close();
                            }
                            catch (Exception) { }
                        }
                    }

                    // Keep track of the highest revision so we can go one higher when we're finished
                    if (_poolVersion <= pair.Value.PoolVersion)
                        _poolVersion = pair.Value.PoolVersion + 1;
                }
                // All pools are cleared and we have a new highest version number to force all old version active items to get discarded
                // instead of going back to the queue when they are closed.
                // We can get away with this because we've pumped up the _poolVersion out of range of all active connections, so they
                // will all get discarded when they try to put themselves back in their pool.
                _connectionPools.Clear();
            }
        }

        /// <summary>
        /// Clear a given pool for a given filename.  Discards anything in the pool for the given file, and revs the pool
        /// version so current active objects on the old version of the pool will get discarded rather than be returned to the pool.
        /// </summary>
        /// <param name="fileName">The filename of the pool to clear</param>
        internal static void ClearPool(string connectionString)
        {
            lock (_connectionPools)
            {
                Pool queue;
                if (_connectionPools.TryGetValue(connectionString, out queue) == true)
                {
                    queue.PoolVersion++;
                    while (queue.Queue.Count > 0)
                    {
                        WeakReference cnn = queue.Queue.Dequeue();
                        EfzConnectionProxy hdl = cnn.Target as EfzConnectionProxy;
                        if (hdl != null)
                        {
                            try
                            {
                                hdl.Close();
                            }
                            catch (Exception) { }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Return a connection to the pool for someone else to use.
        /// </summary>
        /// <param name="fileName">The filename of the pool to use</param>
        /// <param name="hdl">The connection handle to pool</param>
        /// <param name="version">The pool version the handle was created under</param>
        /// <remarks>
        /// If the version numbers don't match between the connection and the pool, then the handle is discarded.
        /// </remarks>
        internal static void Add(string connectionString, EfzConnectionProxy hdl, int version)
        {
            lock (_connectionPools)
            {
                // If the queue doesn't exist in the pool, then it must've been cleared sometime after the connection was created.
                Pool queue;
                if (_connectionPools.TryGetValue(connectionString, out queue) == true && version == queue.PoolVersion &&
                    hdl.Created + hdl.LifeTime > DateTime.UtcNow.Ticks)
                {
                    ResizePool(queue, true);
                    hdl.IsPooled = true;
                    queue.Queue.Enqueue(new WeakReference(hdl, false));
                    GC.KeepAlive(hdl);
                }
                else
                {
                    try
                    {
                        hdl.Close();
                    }
                    catch (Exception) { }
                }
            }
        }

        /// <summary>
        /// We don't have to thread-lock anything in this function, because it's only called by other functions above
        /// which already have a thread-safe lock.
        /// </summary>
        /// <param name="queue">The queue to resize</param>
        /// <param name="forAdding">If a function intends to add to the pool, this is true, which forces the resize
        /// to take one more than it needs from the pool</param>
        private static void ResizePool(Pool queue, bool forAdding)
        {
            int target = queue.MaxPoolSize;
            if (!forAdding && target > 0)
                target++;
            else if (forAdding && target > 0) 
                target--;

            while (queue.Queue.Count > target)
            {
                WeakReference cnn = queue.Queue.Dequeue();
                EfzConnectionProxy hdl = cnn.Target as EfzConnectionProxy;
                if (hdl != null)
                {
                    try
                    {
                        hdl.Close();
                    }
                    catch (Exception) { }
                }
            }

            target = queue.MinPoolSize;
            if (!forAdding && target > 0)
                target++;
            else if (forAdding && target > 0) 
                target--;
            
            while (queue.Queue.Count < target)
            {
                EfzConnectionOptions options = new EfzConnectionOptions(queue.ConnectionString);
                EfzConnectionProxy hdl = new EfzConnectionProxy(options);
                hdl.IsPooled = true;
                queue.Queue.Enqueue(new WeakReference(hdl, false));
                GC.KeepAlive(hdl);
            }
        }
     
    }
}
